/*
Pyxis 2.0
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.Net.NetworkInformation;

using GHIElectronics.NETMF.Net;

using Skewworks.Pyxis.EXT;
using Skewworks.Pyxis.GUI.Controls;

namespace Skewworks.Pyxis.Kernel
{

    [Serializable]
    public delegate void OnNetworkOnline();

    [Serializable]
    public delegate void OnNetworkOffline();

    public class NetworkManager : MarshalByRefObject
    {

        #region Variables

        private NetworkConnectionType _auto = NetworkConnectionType.None;
        private bool _bEthernet = false;

        private PyxisAPI API;

        #endregion

        #region Constructor

        public NetworkManager(PyxisAPI api)
        {
            API = api;
            NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
            if (SystemInfo.SystemID.SKU == 3)
            {
                _bEthernet = true;
                return;
            }
            if (!Ethernet.IsEnabled) Ethernet.Enable();
        }

        #endregion

        #region Events

        public event OnNetworkOnline ConnectionEstablished;

        /// <summary>
        /// Event for Taps
        /// </summary>
        /// <param name="sender"></param>
        protected virtual void OnNetworkOnline()
        {
            if (ConnectionEstablished != null) ConnectionEstablished();
        }

        public event OnNetworkOffline ConnectionRemoved;

        /// <summary>
        /// Event for Taps
        /// </summary>
        /// <param name="sender"></param>
        protected virtual void OnNetworkOffline()
        {
            if (ConnectionRemoved != null) ConnectionRemoved();
        }

        #endregion

        #region Properties

        public NetworkConnectionType AutoConnect
        {
            get { return _auto; }
            set
            {
                _auto = value;
                if (value != NetworkConnectionType.None)
                {
                    Ethernet_AvailabilityChanged(Ethernet.IsCableConnected);
                }
            }
        }

        public bool NetworkConnected
        {
            get
            {
                // WiFi support will be added in futer
                return _bEthernet;
            }
        }

        public bool EthernetConnected
        {
            get { return _bEthernet; }
        }

        public string EthernetGatewayAddress
        {
            get
            {
                try
                {
                    return NetworkInterface.GetAllNetworkInterfaces()[0].GatewayAddress;
                }
                catch (Exception)
                {
                    return "0.0.0.0";
                }
            }
        }

        public string EthernetIPAddress
        {
            get
            {
                try
                {
                    return NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress;
                }
                catch (Exception)
                {
                    return "127.0.0.1";
                }
            }
        }

        public string EthernetSubnetMask
        {
            get
            {
                try
                {
                    return NetworkInterface.GetAllNetworkInterfaces()[0].SubnetMask;
                }
                catch (Exception)
                {
                    return "255.255.255.0";
                }
            }
        }

        #endregion

        #region Public Methods

        public byte[] DownloadURL(string url)
        {
            return DownloadURL(url, "Pyxis/2.0 (Pyxis; U; Pyxis 2.0; en-US; rv:2.0.0.5)");
        }

        public byte[] DownloadURL(string url, string useragent)
        {

            HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
            request.UserAgent = useragent;
            request.KeepAlive = false;
            WebResponse resp = request.GetResponse();

            // Get the network response stream to read the page data.
            if (resp != null)
            {
                Stream respStream = resp.GetResponseStream();
                byte[] byteData;
                int bytesRead = 0;
                Decoder UTF8decoder = Encoding.UTF8.GetDecoder();
                int totalRead = 0;

                // allow 5 seconds for reading the stream
                respStream.ReadTimeout = 5000;

                // If we know the content length, read exactly that amount of 
                // data; otherwise, read until there is nothing left to read.
                if (resp.ContentLength != -1)
                {
                    byteData = new byte[resp.ContentLength];
                    for (int dataRem = (int)resp.ContentLength; dataRem > 0; )
                    {
                        Thread.Sleep(500);
                        bytesRead = respStream.Read(byteData, totalRead, byteData.Length);

                        if (bytesRead == 0)
                            throw new Exception("Data less than expected");

                        totalRead += bytesRead;
                        dataRem -= bytesRead;
                    }

                }
                else
                {
                    // Attempt to read chunks
                    byteData = new byte[1024];
                    string strOut = string.Empty;

                    while (true)
                    {
                        Thread.Sleep(500);

                        if (byteData.Length > respStream.Length)
                            byteData = new byte[respStream.Length];

                        bytesRead = respStream.Read(byteData, 0, byteData.Length);
                        if (bytesRead == 0)
                        {
                            byteData = UTF8Encoding.UTF8.GetBytes(strOut);
                            break;
                        }
                        else
                            strOut += new string(UTF8Encoding.UTF8.GetChars(byteData));

                    }

                }

                // Close the response stream.  For Keep-Alive streams, the 
                // stream will remain open and will be pushed into the unused 
                // stream list.
                resp.Close();
                request.Dispose();

                return byteData;
            }


            return null;
        }

        public int DownloadFile(string url, string file)
        {
            return DownloadFile(url, file, null);
        }

        public int DownloadFile(string url, string file, Progressbar progbar, int bufferSize = 1024)
        {
            try
            {
                // Ensure Directory
                if (!Directory.Exists(Path.GetDirectoryName(file)))
                    Directory.CreateDirectory(Path.GetDirectoryName(file));

                // Perpare for file
                if (File.Exists(file))
                {
                    File.Delete(file);
                    API.MyDrives.FlushFileSystems();
                }
                FileStream FS = new FileStream(file, FileMode.CreateNew, FileAccess.Write);


                byte[] b = new byte[bufferSize];

                HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
                request.KeepAlive = false;
                WebResponse resp = request.GetResponse();

                // Get the network response stream to read the page data.
                if (resp != null)
                {
                    Stream respStream = resp.GetResponseStream();
                    int bytesRead = 0;
                    Decoder UTF8decoder = System.Text.Encoding.UTF8.GetDecoder();
                    int totalRead = 0;

                    // allow 5 seconds for reading the stream
                    respStream.ReadTimeout = 5000;

                    // If we know the content length, read exactly that amount of 
                    // data; otherwise, read until there is nothing left to read.
                    if (resp.ContentLength != -1)
                    {
                        if (progbar != null)
                        {
                            progbar.Maximum = (int)resp.ContentLength;
                            progbar.Value = 0;
                        }
                        for (int dataRem = (int)resp.ContentLength; dataRem > 0; )
                        {
                            //Thread.Sleep(50);
                            bytesRead = respStream.Read(b, 0, (dataRem > b.Length) ? b.Length : dataRem);
                            FS.Write(b, 0, bytesRead);

                            if (bytesRead == 0)
                                throw new Exception("Data less than expected");

                            if (progbar != null) progbar.Value += bytesRead;
                            totalRead += bytesRead;
                            dataRem -= bytesRead;
                        }

                    }
                    else
                    {
                        // Attempt to read chunks
                        b = new byte[1024];
                        string strOut = string.Empty;

                        while (true)
                        {
                            Thread.Sleep(500);

                            if (b.Length > respStream.Length)
                                b = new byte[respStream.Length];

                            bytesRead = respStream.Read(b, 0, b.Length);
                            if (bytesRead == 0)
                            {
                                break;
                            }
                            else
                                FS.Write(b, 0, bytesRead);

                        }

                    }



                    // Close the response stream.  For Keep-Alive streams, the 
                    // stream will remain open and will be pushed into the unused 
                    // stream list.
                    resp.Close();
                    request.Dispose();

                    FS.Close();
                    API.MyDrives.FlushFileSystems();
                    return totalRead;
                }

                return 0;
            }
            catch (Exception e)
            {
                // This is OK inside the emulator where there's no drive
                if (DeviceManager.ActiveDevice != DeviceType.Emulator)
                {
                    API.Prompt("Failed to download file '" + url + "'\n" + e.Message, PyxisAPI.Product, PromptType.OKOnly);
                    return 0;
                }
            }

            return 0;
        }

        public void SetStaticIP(string IP, string Subnet, string Gateway)
        {
            // Get the Ethernet Network Interface
            NetworkInterface netif = NetworkInterface.GetAllNetworkInterfaces()[0];

            // Disable DHCP
            if (netif.IsDhcpEnabled)
            {
                netif.ReleaseDhcpLease();
            }

            netif.EnableStaticIP(IP, Subnet, Gateway);
        }

        #endregion

        private void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            Ethernet_AvailabilityChanged(e.IsAvailable);
        }

        private void Ethernet_AvailabilityChanged(bool IsAvailable)
        {
            if (IsAvailable)
            {
                NetworkInterface netif = NetworkInterface.GetAllNetworkInterfaces()[0];

                if (Ethernet.IsCableConnected)
                {
                    switch (_auto)
                    {
                        case NetworkConnectionType.DHCP:
                            if (!netif.IsDhcpEnabled)
                                netif.EnableDhcp();// This function is blocking
                            else
                            {
                                netif.RenewDhcpLease();// This function is blocking
                            }

                            _bEthernet = true;
                            OnNetworkOnline();
                            break;
                        case NetworkConnectionType.StaticIP:
                            if (netif.IsDhcpEnabled)
                                netif.ReleaseDhcpLease();

                            netif.EnableStaticIP(SettingsManager.BootSettings.IPAddress, SettingsManager.BootSettings.SubnetMask, SettingsManager.BootSettings.GatewayAddress);
                            _bEthernet = true;
                            OnNetworkOnline();

                            break;
                    }
                }
            }
            else
            {
                _bEthernet = false;
                OnNetworkOffline();
            }
        }

    }
}
